package com.ruoyi.web.aspects;

import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import com.ruoyi.web.annotations.ApiLog;
import com.ruoyi.core.constant.Constant;
import com.ruoyi.web.context.RequestContextHolder;
import com.ruoyi.web.domain.SysLogOper;
import com.ruoyi.web.mapper.SysLogOperMapper;
import com.ruoyi.core.util.JsonUtil;
import com.ruoyi.core.util.RequestContextUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.Date;
import java.util.Map;
import java.util.StringJoiner;

@Slf4j
@Aspect
//@Component
public class LogAspect {

    @Autowired
    private SysLogOperMapper logOperMapper;

    /**
     * 排除敏感属性字段
     */
    public static final String[] EXCLUDE_PROPERTIES = {"password", "oldPassword", "newPassword", "confirmPassword"};


    /**
     * 计算操作消耗时间
     */
    private static final ThreadLocal<StopWatch> TIME_THREADLOCAL = new ThreadLocal<>();

    @Pointcut("@within(com.ruoyi.web.annotations.ApiLog)")
    public void pointcut() {
    }

    /**
     * 处理请求前执行
     */
    @Before("pointcut()")
    public void doBefore(JoinPoint joinPoint) {
        StopWatch stopWatch = new StopWatch();
        TIME_THREADLOCAL.set(stopWatch);
        stopWatch.start();
    }

    /**
     * 处理完请求后执行
     *
     * @param joinPoint 切点
     */
    @AfterReturning(pointcut = "pointcut()", returning = "jsonResult")
    public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) {
        ApiLog apiLog = getAnnotation(joinPoint, ApiLog.class);
        handleLog(joinPoint, apiLog, null, jsonResult);
    }

    /**
     * 拦截异常操作
     *
     * @param joinPoint 切点
     * @param e         异常
     */
    @AfterThrowing(value = "pointcut()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
        ApiLog apiLog = getAnnotation(joinPoint, ApiLog.class);
        handleLog(joinPoint, apiLog, e, null);
    }

    protected void handleLog(final JoinPoint joinPoint, ApiLog apiLog, final Exception e, Object jsonResult) {
        try {

            // *========数据库日志=========*//
            HttpServletRequest request = RequestContextUtil.getRequest();
            SysLogOper operLog = new SysLogOper();
            operLog.setStatus(Constant.YES);
            operLog.setOperTime(new Date());
            operLog.setIpaddr(RequestContextUtil.getRequestIp(request));
            operLog.setBrowser(RequestContextUtil.getRequestBrowser(request));
            operLog.setUrl(RequestContextUtil.getRequestUrl(request));
            operLog.setUserName(RequestContextHolder.currentUserName());

            if (e != null) {
                operLog.setStatus(Constant.NO);
                operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
            }
            // 处理 操作模块、操作类型、入参、出参
            getControllerMethodDescription(joinPoint, apiLog, operLog, jsonResult);
            // 设置消耗时间
            StopWatch stopWatch = TIME_THREADLOCAL.get();
            stopWatch.stop();
            operLog.setOperDuration((int) stopWatch.getTime());
            logOperMapper.insert(operLog);
        } catch (Exception exp) {
            // 记录本地异常日志
            log.error("记录操作日志异常:{}", exp.getMessage());
            exp.printStackTrace();
        } finally {
            TIME_THREADLOCAL.remove();
        }
    }

    /**
     * 获取注解中对方法的描述信息 用于Controller层注解
     *
     * @param apiLog  日志
     * @param operLog 操作日志
     */
    private void getControllerMethodDescription(JoinPoint joinPoint, ApiLog apiLog, SysLogOper operLog, Object jsonResult) {


        String className = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();

        // 操作模块
        operLog.setOperMod(className);
        // 操作类型
        operLog.setOperType(methodName);
        // 是否需要保存request，参数和值
        if (apiLog.requestData()) {
            // 获取参数的信息，传入到数据库中。
            setRequestValue(joinPoint, operLog);
        }
        // 是否需要保存response，参数和值
        if (apiLog.responseData() && ObjectUtil.isNotNull(jsonResult)) {
            operLog.setOperResult(JsonUtil.toJson(jsonResult));
        }
    }

    /**
     * 获取请求的参数，放到log中
     *
     * @param operLog 操作日志
     */
    private void setRequestValue(JoinPoint joinPoint, SysLogOper operLog) {
        HttpServletRequest request = RequestContextUtil.getRequest();
        Map<String, String> paramsMap = RequestContextUtil.getRequestParam(request);
        if (MapUtil.isEmpty(paramsMap) && HttpMethod.PUT.name().equals(request.getMethod()) || HttpMethod.POST.name().equals(request.getMethod())) {
            String params = argsArrayToString(joinPoint.getArgs());
            operLog.setOperParam(params);
        } else {
            MapUtil.removeAny(paramsMap, EXCLUDE_PROPERTIES);
            operLog.setOperParam(JsonUtil.toJson(paramsMap));
        }
    }

    /**
     * 参数拼装
     */
    private String argsArrayToString(Object[] paramsArray) {
        StringJoiner params = new StringJoiner(" ");
        if (ArrayUtil.isEmpty(paramsArray)) {
            return params.toString();
        }
        for (Object o : paramsArray) {
            if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) {
                String str = JsonUtil.toJson(o);
                Map dict = JsonUtil.toMap(str);
                if (MapUtil.isNotEmpty(dict)) {
                    MapUtil.removeAny(dict, EXCLUDE_PROPERTIES);
                    str = JsonUtil.toJson(dict);
                }
                params.add(str);
            }
        }
        return params.toString();
    }

    /**
     * 判断是否需要过滤的对象。
     *
     * @param o 对象信息。
     * @return 如果是需要过滤的对象，则返回true；否则返回false。
     */
    @SuppressWarnings("rawtypes")
    private boolean isFilterObject(final Object o) {
        Class<?> clazz = o.getClass();
        if (clazz.isArray()) {
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
        } else if (Collection.class.isAssignableFrom(clazz)) {
            Collection collection = (Collection) o;
            for (Object value : collection) {
                return value instanceof MultipartFile;
            }
        } else if (Map.class.isAssignableFrom(clazz)) {
            Map map = (Map) o;
            for (Object value : map.values()) {
                return value instanceof MultipartFile;
            }
        }
        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
                || o instanceof BindingResult;
    }

    private <T extends Annotation> T getAnnotation(JoinPoint joinPoint, Class<T> clazz) {
        //取方法上面的注解
        T annotation = ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(clazz);
        if (annotation == null) {
            //再尝试取类上面的注解
            annotation = joinPoint.getTarget().getClass().getAnnotation(clazz);
        }
        return annotation;
    }
}
